﻿/******************************************************
* author :  cwj
* email  :  chenwenji_360@live.com 
* history:  created by cwj 2015/7/26 7:42:36 
* clrversion :4.0.30319.18444
******************************************************/

using Machine.DataAccess.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;

namespace Machine.DataAccess.Common.ORM
{
    class SqliteSchemInfo : DbSchemInfoBase
    {
        //private static Lazy<SqliteSchemInfo> _instance = new Lazy<SqliteSchemInfo>(() =>
        //    {
        //        return new SqliteSchemInfo();
        //    }, true);
        //public static SqliteSchemInfo Instance { get { return _instance.Value; } }
        public SqliteSchemInfo(ProviderElement providerElement):base(providerElement) { }

        private static readonly string get_primaryKey_Type = @"select name from sqlite_sequence";
        public static readonly string get_primaryKey_Name = @"PRAGMA table_info({0})";
        public static readonly string get_foreignKey = @"PRAGMA foreign_key_list({0})";
        public static readonly string get_allTables = @"select * from sqlite_master";
        private static readonly string extist_table = @"select count(*) from sqlite_master where type='table' and name='{0}'";

        private static ConcurrentDictionary<string, string> primaryKeyCache = new ConcurrentDictionary<string, string>();
        private static ConcurrentDictionary<string, bool> primaryKeyTypeCache = new ConcurrentDictionary<string, bool>();
        private static ConcurrentDictionary<string, DbForeignKeyInfo[]> fkCache = new ConcurrentDictionary<string, DbForeignKeyInfo[]>();

        //protected override string GetTableName(Type type)
        //{
        //    if (type.Name.StartsWith("<>")) throw new ArgumentException("没有匿名类型的表结构");
        //    //var tableAttribute = type.GetCustomAttributes(typeof(DbTableAttribute), true).FirstOrDefault() as DbTableAttribute;
        //    //if (tableAttribute != null) return tableAttribute.TableName.ToLower();
        //    return type.GetTableName();
        //}

        public override string GetPrimaryKey(string tableName)
        {
            if (!primaryKeyCache.ContainsKey(tableName))
            {
                var sql = string.Format(get_primaryKey_Name, tableName);
                var colums = new DataReader<PrimaryKeyTemp>(new TranslateResult(sql, new List<DbParameter>()),this.ProviderElement,null).ToList();
                if (colums.Count == 0) return null;
                primaryKeyCache[tableName] = colums.First(x => x.PK).Name.ToLower();
            }
            return primaryKeyCache[tableName];
            //return DataResult.GetResult<string>(new TranslateResult(sql, new List<DbParameter>()));
        }

        public override bool GetPrimaryKeyType(string tableName)
        {
            if (!this.ExtistTable("sqlite_sequence"))
            {
                primaryKeyTypeCache[tableName] = false;
            } 
            else if (!primaryKeyTypeCache.ContainsKey(tableName))
            {
                //var parameters = new List<DbParameter>();
                var temp = new DataReader<AutoKeyTemp>(new TranslateResult(get_primaryKey_Type, new DbParameter[0]),this.ProviderElement,null).ToList();
                foreach (var name in temp)
                {
                    if (!primaryKeyTypeCache.ContainsKey(name.Name)) primaryKeyTypeCache[name.Name] = true;
                }
                if (!primaryKeyTypeCache.ContainsKey(tableName)) primaryKeyTypeCache[tableName] = false;
            }
            return primaryKeyTypeCache[tableName];
        }

        public override DbForeignKeyInfo[] GetForeignKey(string tableName)
        {
            if (!fkCache.ContainsKey(tableName) || fkCache[tableName] == null)
            {
                var sql = string.Format(get_foreignKey, tableName);
                fkCache[tableName] = new DataReader<DbForeignKeyInfo>(new TranslateResult(sql, new DbParameter[0]), this.ProviderElement, null).ToArray();
            }
            return fkCache[tableName];
        }

        public override string[] GetAllTables()
        {
            return new DataReader<TableTemp>(new TranslateResult(get_allTables, new DbParameter[0]),this.ProviderElement,null).Select(x => x.Name).ToArray();
        }

        public override Tuple<string, DbForeignKeyInfo[]> GetOuterFKey(Type bigType, Type collectionType)
        {
            var bigTypeName = bigType.GetTableName();
            var collectionTypeName = collectionType.GetTableName();
            var outerFk = this.GetForeignKey(collectionTypeName).ToDictionary(x => x.Table);
            if (outerFk.ContainsKey(bigTypeName)) return new Tuple<string, DbForeignKeyInfo[]>(collectionTypeName, new DbForeignKeyInfo[] { outerFk[bigTypeName] });

            var tableNames = this.GetAllTables();
            foreach (var tableName in tableNames)
            {
                this.GetForeignKey(tableName);
            }

            foreach (var item in fkCache)
            {
                var fkDic = item.Value.ToDictionary(x => x.Table);
                if (fkDic.ContainsKey(bigTypeName) && fkDic.ContainsKey(collectionTypeName))
                    return new Tuple<string, DbForeignKeyInfo[]>(item.Key,
                        new DbForeignKeyInfo[] { fkDic[bigTypeName], fkDic[collectionTypeName] });
            }
            return null;
            //throw new Exception("找不到这样的外键");
        }
        public override bool ExtistTable(string name)
        {
            var sql = string.Format(extist_table, name);
            return DataResult.GetResult<int>(new TranslateResult(sql, (new DbParameter[0]).ToArray()), this.ProviderElement) > 0;
        }

        protected override ConcurrentSet<string> GetTableColums(string tableName)
        {
            var sql = string.Format(get_primaryKey_Name, tableName);
            var colums = new DataReader<PrimaryKeyTemp>(new TranslateResult(sql, new List<DbParameter>()), this.ProviderElement, null).ToList();
            ConcurrentSet<string> obj = new ConcurrentSet<string>();
            var fk = this.GetForeignKey(tableName).ToDictionary(x => x.From);
            foreach (var item in colums)
            {
                if (item.PK == true || fk.ContainsKey(item.Name)) continue;
                obj.Add(item.Name.ToLower());
            }
            return obj;
        }

        protected override void RemoveFkCache(string type)
        {
            fkCache[type] = null;
        }

        private class PrimaryKeyTemp
        {
            public string Name { get; set; }
            public bool PK { get; set; }
        }

        private class AutoKeyTemp
        {
            public string Name { get; set; }
        }

        [DbTable("sqlite_master")]
        private class TableTemp
        {
            public string Name { get; set; }
        }
    }
}
